"""
Description: FramedApp and Action classes for txFramedWeb.
Author: Yaroslav Fedevych
Created: 2010-07-20
License: MIT/X License
"""

from twisted.web.resource import Resource, NoResource
from twisted.web.server import NOT_DONE_YET
from zope.interface import Interface, implements
from twisted.internet.defer import Deferred, maybeDeferred, fail
from asyncjson import AsyncJSON
from twisted.python import log

class IAction(Interface):
    """
    Every action must implement this interface.
    """

class Action(object):
    """
    An action is a single "leaf" in the resource tree. A FramedApp is a Nevow page
    with one or more actions as its leaves. A VirtualHost can have multiple applications.
    If you want applications nested in each other, use Routes' mapper for that.
    """
    implements(IAction)
    name = None
    resource = None

    def execute(self, request):
        """
        The main gear for the action. Do all necessary controller work and return a response.
        
        Returns: C{str} or C{Deferred}
        """
        request.setHeader("Content-type", "text/html;charset=utf-8")
        return "<html><head><title>%s</title></head><body><h1>It works!</h1><p>Executed action named %s</p><p>Postpath = %s</p></body></html>" % \
               (self.name, self.name, repr(request.postpath))

    def postExecute(self, result, request):
        """
        Any post-processing is done here.
        
        Returns: C{str} or C{Deferred}
        """
        return result

    def __execute__(self, request):
        """
        This method gets called by the FramedApp itself as it dispatches the request.
        It is not meant to be overridden.
        """
        result = self.execute(request)
        if isinstance(result, Deferred):
            result.addCallback(self.postExecute, request)
            return result
        else:
            return self.postExecute(result, request)

class NotFoundAction(Action):
    """
    The default 404 action.
    """
    def execute(self, request):
        """
        The main gear for the action. Do all necessary controller work and return a response.
        
        Returns: C{str} or C{Deferred}
        """
        log.err("Error 404 Not Found %s" % request.path)
        request.setResponseCode(404)
        request.setHeader("Content-type", "text/html;charset=utf-8")
        return ("<html><head><title>404 Object Not Found</title></head><body><h1>404 Object Not Found</h1>" + \
               "<p>Executed action named %s</p>" + \
               "<p>Postpath = %s</p>" + \
               "</body></html>") % \
               (self.name, request.postpath)

class JSONAction(Action):
    def postExecute(self, result, request):
        d = AsyncJSON(result).beginProducing(request)
        d.addCallback(lambda(ignored):'')
        return d

class FailingAction(Action):
    def execute(self, request):
        return fail()

class FramedAppMetaclass(type):
    """
    The metaclass for FramedApp. Makes a map for all its actions.
    """
    def __new__(cls, name, bases, dct):
        clsinstance = type.__new__(cls, name, bases, dct)
        for key, value in clsinstance.__dict__.iteritems():
            if isinstance(value, Action):
                clsinstance.__actions__[key] = value
        print cls, clsinstance.__actions__
        return clsinstance

class FramedApp(Resource, object):
    """
    The application resource. You will need to subclass it for your own
    applications, as in
    
    C{class MyApp(FramedApp):
       action1 = SomeAction()
       action2 = SomeOtherAction()
       ...}
    
    """
    __metaclass__ = FramedAppMetaclass
    __actions__ = {}
    __description__ = "FramedWeb application version 0.1"
    isLeaf = False
    name = None
    default_actions = { 'default': Action(), '404': NotFoundAction() }
    _requestMangler = None
    fail = FailingAction()

    def __init__(self, *args, **kwargs):
        """
        Initialize. 
        
        Iterates through actions declared in itself and creates a map of them
        for easy access later. Accepts a C{mangler} keyword option which, if specified, must
        be a callable which accepts a Request object as its argument, performs anything on
        it (say, applies a URL rewriting scheme) and returns either the modified request or
        a Deferred which will finally fire with the modified Request object. 
        """
        Resource.__init__(self, *args, **kwargs)
        for k, action in self.__actions__.iteritems():
            action.resource = self
            action.name = k
        for k, action in self.default_actions.iteritems():
            action.resource = self
            action.name = k
        if kwargs.get('mangler') is not None:
            self._requestMangler = kwargs.get('mangler')

    def dispatch(self, request):
        """
        Dispatches the request to one of actions. It is assumed that pre-dispatching was done to
        determine that the request destination is this application.
        """
        def _cbDispatch(request):
            if len(request.postpath) > 0 and request.postpath[0] in self.__actions__.keys():
                return self.__actions__[request.postpath[0]].__execute__(request)
            elif len(request.postpath) == 0 or (len(request.postpath) == 1 and request.postpath[0] == ''):
                return self.default_actions['default'].__execute__(request)
            return self.default_actions['404'].__execute__(request)


        if self._requestMangler is not None:
            callable = self._requestMangler
            return maybeDeferred(callable(request)).addCallback(_cbDispatch)
        return _cbDispatch(request)

    def _cbFinishRequest(self, result, request):
        """
        Finally write the data to the request and finish it. The callback which 
        is added finally to the action.
        """
        request.write(result)
        request.finish()

    def _ebFinishRequest(self, result, request):
        """
        Signal there was an error while executing the request.
        """
        request.setResponseCode(500)
        log.err(str(result))
        error = "<html><head><title>Internal Server Error</title></head>" + \
            "<body><h1>500 Internal Server Error</h1><p>Please see the error log for details</p>" + \
            "</body></html>"
        request.write(error)
        request.finish()

    def renderHTTP(self, request):
        """
        The application's entry point. 
        
        @param request: Request 
        @var result: Deferred
        """
        result = maybeDeferred(self.dispatch, request)
        result.addCallback(self._cbFinishRequest, request)
        result.addErrback(self._ebFinishRequest, request)
        return NOT_DONE_YET

    def render_GET(self, request):
        return self.renderHTTP(request)

    def render_POST(self, request):
        return self.renderHTTP(request)

    def getChildWithDefault(self, name, request):
        log.msg("getChild(%s)" % name)
        if name in self.__actions__.keys():
            return self
        else:
            return Resource.getChildWithDefault(self, name, request)

class ErrorApplication(FramedApp):
    default = NotFoundAction()

    def dispatch(self, request):
        print request.postpath
        return self.default.__execute__(request)

